 package com.trippin.DB;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.beoui.geocell.GeocellManager;
import com.beoui.geocell.model.BoundingBox;
import com.beoui.geocell.model.Point;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.trippin.DB.entities.AttractionEntity;
import com.trippin.DB.entities.Comment;
import com.trippin.DB.entities.ScheduledAttractionEntity;
import com.trippin.DB.entities.SharingRequest;
import com.trippin.DB.entities.StickyNoteEntity;
import com.trippin.DB.entities.TripEntity;
import com.trippin.DB.entities.TripEntity.ShortDetailedTrip;
import com.trippin.DB.entities.UserNotification.NotificationType;
import com.trippin.DB.entities.TrippinEntity;
import com.trippin.DB.entities.UserEntity;
import com.trippin.DB.entities.AttractionEntity.ShortDetailedAttraction;
import com.trippin.DB.entities.UserNotification;
import com.trippin.DB.entities.UserSuggestion;
import com.trippin.Utils.ScheduledAttractionEntityComparator;
import com.trippin.Utils.ServletUtil;
import com.trippin.Utils.StickyNoteComparator;
import com.trippin.server.AttractionType;
import com.trippin.server.AttractionTypeCluster;


public class DataStoreHandler extends DBHandler {

	private static final Logger logger = Logger.getLogger(DataStoreHandler.class.getCanonicalName());

	private static DataStoreHandler instance = null;

	//synchronous cache 
	private MemcacheService cache = MemcacheServiceFactory.getMemcacheService();

	private DataStoreHandler() {
		cache.clearAll();
	}

	public static DataStoreHandler getInstance() {
		if (instance == null)
			instance = new DataStoreHandler();
		return instance;
	}


	private synchronized PersistenceManager getPersistenceManager() {
		return PMF.getInstance().getPersistenceManager();
	}


	private static boolean DateIsFuture(TimeZone timeZone, Date date) {
		Date currDate = new Date();
		return currDate.before(date) || currDate.equals(date);
	}
	
	private boolean DateInTripRange(TripEntity trip, Date scheduleStart, Date scheduleEnd) {
		Date start = trip.getStart();
		Date end = trip.getEnd();
		return (start.before(scheduleStart) || start.equals(scheduleStart)) && (end.after(scheduleEnd) || end.equals(scheduleEnd));
	}


	private Key createKey(Class<? extends TrippinEntity> entityClass, long Id) {
		Key key = KeyFactory.createKey(entityClass.getSimpleName()/*.toUpperCase()*/,Id);
		return key;
	}


	private Set<ShortDetailedAttraction> queryResultAttractionsToShortDetailed(PersistenceManager pm, Collection<AttractionEntity> attractions, boolean toDetach) {
		List<AttractionEntity> detachedAttrs = new ArrayList<AttractionEntity>();
		if(toDetach) {
		for(AttractionEntity att : attractions) {
			AttractionEntity detachedAttr = attractionCollectionsValidator(pm,att);
			detachedAttrs.add(detachedAttr);

		}	
		}
		else {
			detachedAttrs.addAll(attractions);
		}
		return ServletUtil.toShortAttractions(detachedAttrs);
	}
	
	private TripEntity tripCollectionsValidator(PersistenceManager pm, TripEntity trip, boolean toCache) {
		trip.getAllUsersSharedWith().size();
		trip.getAllUsersSharingInvites().size();
		trip.getNotScheduledYetAttractionsKeys().size();
		trip.getScheduleAttractionsKeys().size();
		
		TripEntity detachedTrip = pm.detachCopy(trip);
		
		if(toCache) {
			//cache.delete(detachedTrip.getId());
			retriveTripAttrsAndScheduleAttrs(pm, detachedTrip);
			cache.put(detachedTrip.getId(), detachedTrip);
		}
		
		return detachedTrip;
	}
	
	private ScheduledAttractionEntity scheduleAttrCollectionsValidator(PersistenceManager pm, ScheduledAttractionEntity scheduleAttr) {
		scheduleAttr.getAttendees().size();
		scheduleAttr.getAttendeesMinuses().size();
		scheduleAttr.getAttendeesPluses().size();
		
		ScheduledAttractionEntity detachedSAE = pm.detachCopy(scheduleAttr);
		cache.delete(detachedSAE.getId());
		cache.put(detachedSAE.getId(), detachedSAE);
		
		return detachedSAE;
	}

	private AttractionEntity attractionCollectionsValidator(PersistenceManager pm, AttractionEntity attr) {
		attr.getFutureAttendees().size();
		attr.getPastAttendees().size();
		attr.getNotScheduledYetAttendees().size();
		attr.getScheduledAttractionsKeys().size();
		attr.getComments().size();
		attr.getLinks().size();
		attr.getAttendeesMinuses().size();
		attr.getAttendeesPluses().size();
		attr.getDuration();
		
		AttractionEntity detachedAttr = pm.detachCopy(attr);
				
		cache.delete(detachedAttr.getId());
		
		//getting the schedule attractions
		updateAttractionScheduleAttrs(pm, detachedAttr);
		
		cache.put(detachedAttr.getId(), detachedAttr);
		
		return detachedAttr;
	}
	
	private UserEntity userCollectionsValidator(PersistenceManager pm, UserEntity user) {
		user.getSharingTripsRequests().size();
		user.getStickyNotes().size();
		
		UserEntity detachedUser = pm.detachCopy(user);
		cache.delete(detachedUser.getUserId());
		cache.put(detachedUser.getUserId(), detachedUser);
		
		return detachedUser;
	}


	//--------------------------------------Attractions Handling--------------------------------------------\\

	@SuppressWarnings("unchecked")
	@Override
	public Set<ShortDetailedAttraction> getAttractions(long userId, double latN,
			double lonE, double latS, double lonW, Date startTime, Date endTime) {
		BoundingBox bb = new BoundingBox(latN, lonE, latS, lonW);
		
		// Calculate the geocells list to be used in the queries (optimize list of cells that complete the given bounding box)
		List<String> cells = GeocellManager.bestBboxSearchCells(bb, null);

		PersistenceManager pm = getPersistenceManager();

		String queryString = "SELECT FROM com.trippin.DB.entities.AttractionEntity WHERE geocellsParameter.contains(geoCellsData)";
		Query query = pm.newQuery(queryString);
		query.declareParameters("String geocellsParameter");

		Set<ShortDetailedAttraction> shortAttractions = null;
		try {
			List<AttractionEntity> attractions = (List<AttractionEntity>) query.execute(cells);
			//no need to retrieve the attractions schedule attractions, since the this function returns an 
			//Iterable of ShortDetailedAttraction (that don't have the schedule attractions)
			attractions.size();
			shortAttractions = queryResultAttractionsToShortDetailed(pm, attractions, true);			
		} finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}

		return shortAttractions;
	}

	@Override
	public List<ScheduledAttractionEntity> getScheduleOfAttraction (
			long userId, long attrId, Date start, Date end) {
		
		PersistenceManager pm = getPersistenceManager();

		Query query = null;
		List<ScheduledAttractionEntity> detachedScheduleAttrs = new LinkedList<ScheduledAttractionEntity>();
		try {
			List<ScheduledAttractionEntity> scheduleAttrs = new ArrayList<ScheduledAttractionEntity>();
			
			query = pm.newQuery(ScheduledAttractionEntity.class, "attrId == attrIdParam && startTime >= startDateParam");
			query.declareParameters("Long attrIdParam, java.util.Date startDateParam");
			@SuppressWarnings("unchecked")
			List<ScheduledAttractionEntity> startResults =  (List<ScheduledAttractionEntity>) query.execute(attrId, start);
			startResults.size();
			List<ScheduledAttractionEntity> startScheduleAttrs = new ArrayList<ScheduledAttractionEntity>(startResults);
			
			query = pm.newQuery(ScheduledAttractionEntity.class, "attrId == attrIdParam && endTime <= endDateParam");
			query.declareParameters("Long attrIdParam, java.util.Date endDateParam");
			@SuppressWarnings("unchecked")
			List<ScheduledAttractionEntity> endResults = (List<ScheduledAttractionEntity>) query.execute(attrId, end);
			endResults.size();
			List<ScheduledAttractionEntity> endScheduleAttrs = new ArrayList<ScheduledAttractionEntity>(endResults);
			
			for(ScheduledAttractionEntity sca : startScheduleAttrs) {
				if(endScheduleAttrs.contains(sca)) {
					scheduleAttrs.add(sca);
				}
			}

			for(ScheduledAttractionEntity sca : scheduleAttrs) {
				ScheduledAttractionEntity detachedScheduleAttr = scheduleAttrCollectionsValidator(pm, sca);
				detachedScheduleAttrs.add(detachedScheduleAttr);
			}
		}
		finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}		
		return detachedScheduleAttrs;
	}

	@Override
	public Iterable<AttractionEntity> getAttractionsContains(String atrName) {
		/*
		 * TODO currently there is no good solution for substring search in the datastore
		 * 		so we are not supporting string search at the moment.
		 */	
		return null;
	}

	@Override
	public AttractionEntity getAttractionById(long userId, long attrId) {
		PersistenceManager pm = getPersistenceManager();

		AttractionEntity returnedAttraction = null;
		try {			
			returnedAttraction = (AttractionEntity) cache.get(attrId);
			if(returnedAttraction == null) {
				AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attrId));
				returnedAttraction = attractionCollectionsValidator(pm, att);						
			}		
		}
		finally {
			pm.close();
		}
		return returnedAttraction;

	}

	@Override
	public AttractionEntity createNewAttraction(long userId,
			AttractionEntity attraction) {

		PersistenceManager pm = getPersistenceManager();		

		AttractionEntity attr = new AttractionEntity();
		attr.initNewAttraction(userId,attraction.getLongitude(),attraction.getLatitude());	
		attr.updateAttraction(attraction);

		AttractionEntity returnedAttraction = null;
		try {
			AttractionEntity att = pm.makePersistent(attr);
			att.setId();

			returnedAttraction = attractionCollectionsValidator(pm, att);
		}
		finally {
			pm.close();
		}
		return returnedAttraction;
	}

	@Override
	public AttractionEntity updateAttraction(long userId, long attId,
			AttractionEntity attraction) {			

		PersistenceManager pm = getPersistenceManager();

		AttractionEntity returnedAttraction = null;
		try {				
			AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attId));
			if((att.getCreaterBy() == userId) && 
					att.getFutureAttendees().isEmpty() && att.getPastAttendees().isEmpty()) {
				att.updateAttraction(attraction);
				returnedAttraction = attractionCollectionsValidator(pm, att);
			}
			else {
				throw new JDOObjectNotFoundException("attraction (" + attId + ") was not found or user has no update privileges");
			}
		}
		finally {
			pm.close();
		}

		return returnedAttraction;
	}


	@Override
	public void deleteAttraction(long userId, long attId) throws IOException {
		PersistenceManager pm = getPersistenceManager();

		try {				
			AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attId));
			if((att != null) && (att.getCreaterBy() == userId) && 
					att.getFutureAttendees().isEmpty() && att.getPastAttendees().isEmpty()) {
				cache.delete(att.getId()); 
				pm.deletePersistent(att);
			}
			else {
				throw new IOException("user has no delete privileges for attraction (" + attId + ")");
			}
		}
		finally {
			pm.close();
		}
	}


	@Override
	public AttractionEntity attendToAttraction(long userId, long tripId, long attId)
			throws IOException {
		return (AttractionEntity) userAttendHelper(userId,tripId,attId,-1,false,false,null);
	}

	@Override
	public AttractionEntity unAttendFromAttraction(long userId, long tripId, long attId) {
		return (AttractionEntity) userUnAttendHelper(userId,tripId,attId,-1,false, true);
	}


	@Override
	public AttractionEntity addCommentToAttraction(long userId, long attrId, Comment comment) {
		PersistenceManager pm = getPersistenceManager();

		AttractionEntity returnedAttraction = null;
		try {			
			AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attrId));						
			Comment comm = comment.createNewComment(userId, comment);
			att.addComment(comm);
			comm.setId();
			returnedAttraction = attractionCollectionsValidator(pm, att);
		}
		finally {
			pm.close();
		}

		return returnedAttraction;
	}

	@Override
	public void removeCommentFromAttraction(long userId, long attrId, long commentId) {	
		PersistenceManager pm = getPersistenceManager();

		try {
			AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attrId));

			Comment comment = att.getComment(commentId);
			if((comment == null) || (comment.getUserId() != userId)) {
				throw new JDOObjectNotFoundException("user (" + userId + ") did not create this comment or this comment does not exist in attraction (" + attrId + ")");
			}
			att.removComment(comment);
			pm.deletePersistent(comment);

			attractionCollectionsValidator(pm, att);
		}
		finally {
			pm.close();
		}
	}


	@Override
	public AttractionEntity addPlusOrMinusToAttraction(long userId, long attrId, boolean addPlus) {
		PersistenceManager pm = getPersistenceManager();

		AttractionEntity returnedAttraction = null;
		try {			
			AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attrId));

			if(addPlus) {
				att.addPlus(userId);
			}
			else {
				att.addMinus(userId);
			}

			returnedAttraction = attractionCollectionsValidator(pm, att);

		}
		finally {
			pm.close();
		}

		return returnedAttraction;
	}


	//-----------------------------Schedule Attractions Handling---------------------------------------\\



	@Override
	public AttractionEntity createNewScheduleAttraction(long userId, long tripId,
			long attrId, Date date) throws IOException {
		return userAttendHelper(userId,tripId,attrId,-1,true,true,date);
	}

	@Override
	public AttractionEntity attendToScheduleAttraction (
			long userId, long tripId, long attrId, long scheduleAttrId) throws IOException {
		return userAttendHelper(userId,tripId, attrId,scheduleAttrId,true,false,null);
	}

	@Override
	public AttractionEntity unAttendFromScheduleAttraction(
			long userId, long tripId, long attId, long scheduleAtrId) {
		return userUnAttendHelper(userId,tripId,attId,scheduleAtrId,true, true);
	}

	@Override
	public ScheduledAttractionEntity addPlusOrMinusToScheduleAttraction(long userId, long scheduleAttrId, boolean addPlus) {
		PersistenceManager pm = getPersistenceManager();

		ScheduledAttractionEntity returnedscheduleAttr = null;
		try {			
			ScheduledAttractionEntity scheduleAttr = pm.getObjectById(ScheduledAttractionEntity.class, 
					createKey(ScheduledAttractionEntity.class, scheduleAttrId));

			if(addPlus) {
				scheduleAttr.addPlus(userId);
			}
			else {
				scheduleAttr.addMinus(userId);
			};

			returnedscheduleAttr = scheduleAttrCollectionsValidator(pm, scheduleAttr);
		}
		finally {
			pm.close();
		}

		return returnedscheduleAttr;
	}
	
	
	private ScheduledAttractionEntity getScheduleAttractionByKey(Key key, PersistenceManager pm) {
		ScheduledAttractionEntity sae = (ScheduledAttractionEntity) cache.get(key.getId());
		if(sae == null) {
			sae = pm.getObjectById(ScheduledAttractionEntity.class, key);
			sae = scheduleAttrCollectionsValidator(pm, sae);
		}
		return sae;
	}


	//--------------------------------------Attractions and Schedule Attractions Helper--------------------------------------------\\

	private AttractionEntity userAttendHelper(long userId, long tripId, long attarctionId, long scheduleAttrId, 
			boolean attendSchedule, boolean createNewSchedule, Date startDate)
					throws IOException {

		PersistenceManager pm = getPersistenceManager();
		 //Transaction tx = pm.currentTransaction();
		/*
		 * adding the schedule attraction (if needed) to the user's relevant trip.
		 * adding the schedule attraction (if needed) to the relevant attraction.
		 * adding the user to the attraction attendees list.
		 * adding the schedule attraction (or the attraction) to the user's relevant trip. 
		 */
		AttractionEntity returnedAttr = null;
		try {
			//tx.begin();
			TripEntity trip = pm.getObjectById(TripEntity.class, createKey(TripEntity.class, tripId));
			if ((trip.getUserId() != userId) && !trip.isUserSharedWith(userId)) {
				throw new JDOObjectNotFoundException("trip (" + tripId + ") does not belong to user " + userId);
			}

			//getting the attraction and all of its scheduled attractions
			AttractionEntity att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attarctionId));		

			if(attendSchedule) {
				//handle schedule attraction attendance 
				ScheduledAttractionEntity scheduledAttr = null;				 

				if(createNewSchedule) {
					if(!DateIsFuture(null, startDate)) {
						throw new IOException(startDate + " is not a future date");
					}
					
					ScheduledAttractionEntity saep = new ScheduledAttractionEntity(userId, attarctionId, att.getTitle(), 
							att.getType(), startDate, att.getDuration(), att.getLatitude(), att.getLongitude());
					
					if(!DateInTripRange(trip,saep.getStartTime(),saep.getEndTime())) {
						throw new IOException(startDate + " is not int the trip dates range");
					}
					saep.addAttendee(userId);
					scheduledAttr = pm.makePersistent(saep);
					scheduledAttr.setId();
					scheduledAttr = scheduleAttrCollectionsValidator(pm, scheduledAttr);
					//logger.log(Level.SEVERE, "the users " + scheduledAttr.getAttendees() + " are attending schedule attraction (" + scheduledAttr.getId() + ")");

					att.addScheduledAttractionKey(scheduledAttr.getKey());					

				}
				else {					
					Key scheduleAttrKey = att.getScheduledAttraction(scheduleAttrId);
					if(scheduleAttrKey == null) {
						throw new JDOObjectNotFoundException("schedule attraction " + scheduleAttrId + " does not exists in this attraction");
					}
					scheduledAttr = pm.getObjectById(ScheduledAttractionEntity.class, scheduleAttrKey);
					
					if(!DateInTripRange(trip,scheduledAttr.getStartTime(),scheduledAttr.getEndTime())) {
						throw new IOException(startDate + " is not int the trip dates range");
					}
					
					scheduledAttr.addAttendee(userId);
					scheduledAttr = scheduleAttrCollectionsValidator(pm, scheduledAttr);
				}
				
				att.addFutureAttendee(userId);
				trip.addScheduleAttractionKey(scheduledAttr.getKey());
				Key notScheduleAttrKey = trip.getNotScheduledYetAttractionsKey(att.getId());
				if(notScheduleAttrKey != null) {
					att.removeNotScheduledYetAttendee(userId);
					trip.removeNotScheduledYetAttractionKey(att.getId()); 
				}
			}
			else {
				//handle attraction attendance (without a schedule/date)
				att.addNotScheduledYetAttendee(userId);
				trip.addNotScheduledYetAttractionKey(att.getKey());				
			}
			
			returnedAttr = attractionCollectionsValidator(pm, att);

			tripCollectionsValidator(pm, trip, true);
			
			//tx.commit();

		} finally {
		//	if(tx.isActive()) {
		//		tx.rollback();
		//	}
			pm.close();
		}

		return returnedAttr;
	}


	private AttractionEntity userUnAttendHelper(long userId, long tripId, long attarctionId, long scheduleAttrId, 
			boolean unAttendSchedule, boolean removeFromTrip) {
		PersistenceManager pm = getPersistenceManager();

		AttractionEntity returnedAttr = null;
		try {
			TripEntity trip = pm.getObjectById(TripEntity.class,createKey(TripEntity.class, tripId));
			if ((trip.getUserId() != userId) && !trip.isUserSharedWith(userId)) {
				throw new JDOObjectNotFoundException("trip (" + tripId + ") does not belong to user (" + userId + ")");
			}
			trip.getScheduleAttractionsKeys().size();
			trip.getNotScheduledYetAttractionsKeys().size();
			
			AttractionEntity att = null;
			if(attarctionId > 0) {
				att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, attarctionId));
			}

			if(unAttendSchedule) {
				ScheduledAttractionEntity scheduledAttr = null;
				Key scheduleAttrKey = null;
				if(att != null) {
					scheduleAttrKey = att.getScheduledAttraction(scheduleAttrId);
					if(scheduleAttrKey == null) {
						throw new JDOObjectNotFoundException("schedule attraction (" + scheduleAttrId + ") does not exists in this attraction");
					}
					scheduledAttr = pm.getObjectById(ScheduledAttractionEntity.class, scheduleAttrKey);
				}
				else {
					scheduleAttrKey = createKey(ScheduledAttractionEntity.class, scheduleAttrId);
					scheduledAttr = pm.getObjectById(ScheduledAttractionEntity.class, scheduleAttrKey);
					att = pm.getObjectById(AttractionEntity.class, createKey(AttractionEntity.class, scheduledAttr.getAttrId()));
				}
				
				scheduledAttr.removeAttendee(userId);
				att.removeFutureAttendee(userId);
				
				if(removeFromTrip) {
					trip.removeScheduleAttractionKey(scheduleAttrId);	
				}

				//if the user was the only attendee of the schedule attraction - delete it.
				if(scheduledAttr.getAttendees().isEmpty()) {
					att.removeScheduleAttractionKey(scheduleAttrKey);
					trip.removeNotScheduledYetAttractionKey(att.getId());
					cache.delete(scheduledAttr.getId());
					pm.deletePersistent(scheduledAttr);
					scheduledAttr = null;
				}
				else {
					//update cache
					scheduleAttrCollectionsValidator(pm, scheduledAttr);
				}

			}
			else {
				att.removeNotScheduledYetAttendee(userId);
				if(removeFromTrip) {
					trip.removeNotScheduledYetAttractionKey(att.getId());	
				}
			}

			returnedAttr = attractionCollectionsValidator(pm, att);
		
			tripCollectionsValidator(pm, trip, true);
			
		} finally {
			pm.close();
		}

		return returnedAttr;
	}


	private void updateAttractionScheduleAttrs(PersistenceManager pm, AttractionEntity Attr) {
		//getting the schedule attractions
		Set<Key> scheduleAttrsKeys = Attr.getScheduledAttractionsKeys();
		logger.log(Level.SEVERE, "getting schedule attractions: " + scheduleAttrsKeys);
		Set<ScheduledAttractionEntity> scheduleAttrs = new HashSet<ScheduledAttractionEntity>();
		if(!scheduleAttrsKeys.isEmpty()) {
			for(Key key : scheduleAttrsKeys) {				
				ScheduledAttractionEntity sae = getScheduleAttractionByKey(key, pm);
				scheduleAttrs.add(sae);
			}
		}
		Attr.setScheduleAttractions(scheduleAttrs);
	}


	//--------------------------------------Trips Handling--------------------------------------------\\

	@Override
	public TripEntity getUserTrip(long userId, long tripId) {
		PersistenceManager pm = getPersistenceManager();

		TripEntity returnedTrip = null;
		try {
			boolean fromCache = true;
			returnedTrip = (TripEntity) cache.get(tripId);
			if(returnedTrip == null) {
				returnedTrip = pm.getObjectById(TripEntity.class, createKey(TripEntity.class, tripId));
				fromCache = false;
			}
			
			if ((returnedTrip.getUserId() != userId) && !returnedTrip.isUserSharedWith(userId)) {
				throw new JDOObjectNotFoundException("trip (" + tripId + ") does not belong to user (" + userId + ")");
			}
		
			if(!fromCache) {
				returnedTrip = tripCollectionsValidator(pm, returnedTrip, true);
			}
		}
		finally {
			pm.close();
		}

		return returnedTrip;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Iterable<ShortDetailedTrip> getAllUserTrips(long userId) {
		PersistenceManager pm = getPersistenceManager();

		Query query1 = null;
		Query query2 = null;
		Set<ShortDetailedTrip> returnedTrips = null;
		try {
			query1 = pm.newQuery(TripEntity.class, "userId == " + userId);
			query2 = pm.newQuery(TripEntity.class, "sharedWith.contains(" + userId + ")");

			List<TripEntity> trips = new LinkedList<TripEntity>();
			trips.addAll((List<TripEntity>) query1.execute());
			trips.addAll((List<TripEntity>) query2.execute());
			List<TripEntity> detachedTrips = new LinkedList<TripEntity>();
			for(TripEntity trip : trips) {
				TripEntity detachedTrip = tripCollectionsValidator(pm, trip, true);
				detachedTrips.add(detachedTrip);
			}
			returnedTrips = ServletUtil.toShortDetailedTrips(detachedTrips);
		}
		finally {
			if(query1 != null) {
				query1.closeAll();
			}
			if(query2 != null) {
				query2.closeAll();
			}
			pm.close();
		}		
		return returnedTrips;
	}

	@Override
	public TripEntity createTrip(long userId, TripEntity trip) throws IOException {
		PersistenceManager pm = getPersistenceManager();		
		
		trip.initTripEntity(userId, trip.getNumOfDays(), trip.getStart(), trip.getEnd());
		TripEntity returnedTrip = null;
		try {
			TripEntity tr = pm.makePersistent(trip);
			tr.setId();

			returnedTrip = tripCollectionsValidator(pm, tr, true);
		}
		finally {
			pm.close();
		}	

		return returnedTrip;
	}


	@Override
	public TripEntity updateTrip(long userId, long tripId, TripEntity trip) throws IOException {
		PersistenceManager pm = getPersistenceManager();

		Query query = null;
		TripEntity returnedTrip = null;
		try {
			query = pm.newQuery(TripEntity.class, "id == " + userId + " && userId == " + tripId);
			@SuppressWarnings("unchecked")
			List<TripEntity> trips = (List<TripEntity>) query.execute();

			if(trips.isEmpty()) {
				throw new JDOObjectNotFoundException("user (" + userId + ") does not have trip (" + tripId + ")");
			}

			TripEntity tr = trips.get(0);
			tr.updateTrip(trip);
			returnedTrip = tripCollectionsValidator(pm, trip, true);

		}
		finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}

		return returnedTrip;	
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void deleteTrip(long userId, long tripId) {
		PersistenceManager pm = getPersistenceManager();

		Query query = null;
		TripEntity detachedTrip;
		boolean owner = true;
		boolean isOnlyOne = false;
		Set<Long> sharedWithSet = null;
		try {
			query = pm.newQuery(TripEntity.class, "id == " + tripId + " && userId == " + userId);
			List<TripEntity> trips = (List<TripEntity>) query.execute();

			if(trips.isEmpty()) {
				query = pm.newQuery(TripEntity.class, "sharedWith.contains(" + userId + ")");
				trips = (List<TripEntity>) query.execute();
				if(trips.isEmpty()) {
					throw new JDOObjectNotFoundException("user (" + userId + ") does not have trip (" + tripId + ")");
				}
				owner = false;				
			}

			TripEntity tr = trips.get(0);
			
			detachedTrip = tripCollectionsValidator(pm, tr, false);
			
			sharedWithSet = detachedTrip.getAllUsersSharedWith();
			if(owner && sharedWithSet.isEmpty()) {
				isOnlyOne = true;
			}

		}
		finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}
	
		for(Key scheduleAttrKey : detachedTrip.getScheduleAttractionsKeys()) {
			userUnAttendHelper(userId, tripId, -1, scheduleAttrKey.getId(), true, isOnlyOne);
		}
		
		for(Key attrKey : detachedTrip.getNotScheduledYetAttractionsKeys()) {
			userUnAttendHelper(userId, tripId,attrKey.getId(),-1, false, isOnlyOne);
		}
		
		pm = getPersistenceManager();		
		try {
			TripEntity tr = pm.getObjectById(TripEntity.class, createKey(TripEntity.class, tripId));
			boolean updateCache = true;
			if(owner) {
				if(sharedWithSet.isEmpty()) {
					pm.deletePersistent(tr);
					updateCache = false;
				}
				else {
					for(long aboutToBeOwner : sharedWithSet) {
						tr.setUserId(aboutToBeOwner);
						tr.removeUserFromSharedWith(aboutToBeOwner);
						break;
					}
				}
			}
			else {
				tr.removeUserFromSharedWith(userId);
			}
						
			if(updateCache) {
				tripCollectionsValidator(pm, tr, true);
			}
			else {
				cache.delete(tripId);
			}
			
		}
		finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}
	}


	private void retriveTripAttrsAndScheduleAttrs(PersistenceManager pm, TripEntity trip) {
		//getting the schedule attractions
		Set<ScheduledAttractionEntity> scheduleAttrs = new TreeSet<ScheduledAttractionEntity>(new ScheduledAttractionEntityComparator());
		for(Key key : trip.getScheduleAttractionsKeys()) {
			scheduleAttrs.add(getScheduleAttractionByKey(key, pm));
		}
		logger.log(Level.SEVERE, "trip attractions: " + scheduleAttrs);
		trip.setScheduleAttractions(scheduleAttrs);

		//getting the not yet scheduled attractions
		Set<AttractionEntity> attractions = new HashSet<AttractionEntity>(); 
		for(Key key : trip.getNotScheduledYetAttractionsKeys()) {
			AttractionEntity detachedAttr = (AttractionEntity) cache.get(key.getId());
			if(detachedAttr == null) {
				AttractionEntity attr = pm.getObjectById(AttractionEntity.class, key);
				detachedAttr = attractionCollectionsValidator(pm, attr);
			}
			attractions.add(detachedAttr);
		}
		Set<ShortDetailedAttraction> shortAttrs = queryResultAttractionsToShortDetailed(pm, attractions, false);	
		trip.setNotScheduledYetAttractions(shortAttrs);			
	}
	
	
	//--------------------------------------Users Handling--------------------------------------------\\

	
	@Override
	public UserEntity getUserById(long userId) {
		PersistenceManager pm = getPersistenceManager();

		UserEntity returnedUser = null;
		try {
			returnedUser = (UserEntity) cache.get(userId);
			if(returnedUser == null) {
				UserEntity user = getUser(pm,userId);
				if(user != null) {
					returnedUser = userCollectionsValidator(pm, user);
				}
			}			
		}
		finally {
			pm.close();
		}

		return returnedUser;
	}

	@Override
	public UserEntity createUser(long userId, UserEntity user) throws IOException {
		return createOrUpdateUser(userId, user, false);
	}

	@Override
	public UserEntity updateUser(long userId, UserEntity user) throws IOException {
		return createOrUpdateUser(userId, user, true);
	}

	private UserEntity createOrUpdateUser(long userId, UserEntity user, boolean update) throws IOException {
		PersistenceManager pm = getPersistenceManager();

		UserEntity returnedUser = null;
		try {		
			UserEntity userEntity = getUser(pm,userId);
			if(update) {
				if(userEntity == null) {
					throw new IOException("user (" + userId + ") is not a registered Trippin Out User");
				}
				userEntity.updateUser(user);				
			}
			else {
				if(userEntity != null) {
					throw new IOException("user (" + userId + ") is already a registered Trippin Out User");
				}
				UserEntity u = new UserEntity(userId);
				u.initUerEntity(user.getName(), user.getGender(), user.getBirthDay(), user.getLanguages());
				
				userEntity = pm.makePersistent(u);
			}

			returnedUser = userCollectionsValidator(pm, userEntity);

		}
		finally {
			pm.close();
		}		
		return returnedUser;
	}
	
	private UserEntity getUser(PersistenceManager pm, long userId) {
		Query query = null;
		try {
			query = pm.newQuery(UserEntity.class, "id == " + userId);
			@SuppressWarnings("unchecked")
			List<UserEntity> users =  (List<UserEntity>) query.execute();
			if(users.isEmpty()) {
				return null;
			}
			
			return users.get(0);
		}
		finally {
			if(query != null) {
				query.closeAll();
			}
		}

	}
	
	//--------------------------------------Notifications Handling--------------------------------------------\\

	@Override
	public UserNotification<ShortDetailedAttraction> getAttractionsUserMayLike(long userId, long tripId) {
		
		TripEntity trip = getUserTrip(userId, tripId);
		
		//create the list of all attraction types and all of their geo points.
		List<AttractionType> attractionsTypes = new ArrayList<AttractionType>();
		List<Point> points = new ArrayList<Point>();		
		for(ScheduledAttractionEntity sae : trip.getScheduleAttractions()) {
			AttractionType type = AttractionType.getAttractionTypeByName(sae.getType());
			attractionsTypes.add(type);
			points.add(new Point(sae.getLatitude(),sae.getLongitude()));
		}
		for(ShortDetailedAttraction ae : trip.getNotScheduledYetAttractions()) {
			AttractionType type = AttractionType.getAttractionTypeByName(ae.getType());
			attractionsTypes.add(type);
			points.add(new Point(ae.getLatitude(),ae.getLongitude()));
		}

		//get the ordered Cluster of a user (According to his attraction types in his current trip
		List<AttractionTypeCluster> orderedClusters = AttractionTypeCluster.getOrderedAttractionTypeCluster(attractionsTypes);
		
		Set<ShortDetailedAttraction> attrsUMayLike = new TreeSet<ShortDetailedAttraction>(new ShortAttrsYouMayLikeComparator(orderedClusters));		
		if(!points.isEmpty()) {
			//get all attraction in a bound that his center is the average of all point, and order them by their clusters
			BoundingBox bb = calculateMapBound(points);

			attrsUMayLike.addAll(getAttractions(userId,bb.getNorth(), bb.getEast(), bb.getSouth(), bb.getWest(), null, null));
		}
		
		List<ShortDetailedAttraction> attrsUMayLikeList = new ArrayList<ShortDetailedAttraction>();
		for(ShortDetailedAttraction sda : attrsUMayLike) {
			if(!sda.isUserAttend(userId)) {
				attrsUMayLikeList.add(sda);
			}
		}
		return new UserNotification<ShortDetailedAttraction>(userId,NotificationType.ATTRACTIONS_YOU_MAY_LIKE,attrsUMayLikeList);		
	}

	
	@Override
	public UserNotification<UserSuggestion> getUsersNearMe(long userId, long tripId) {
		TripEntity trip = getUserTrip(userId, tripId);
		
		List<Point> points = new ArrayList<Point>();
		List<ScheduledAttractionEntity> scheduleOfAttr = null;
		 Map<Long, UserSuggestion> users = new HashMap<Long, UserSuggestion>();
		 
		 Set<ScheduledAttractionEntity> tripScheduleAttrs = trip.getScheduleAttractions();
		 if(tripScheduleAttrs == null) {
			 tripScheduleAttrs = new HashSet<ScheduledAttractionEntity>();
		 }
		 
		//all user with the same schedule
		 addUsersScheduleToMap(userId, tripScheduleAttrs,users, 0);
		 
		 Set<ScheduledAttractionEntity> allScheduleOfAttr = new HashSet<ScheduledAttractionEntity>(tripScheduleAttrs);
		
		 //all user with the same attractions, other schedules
		 for(ScheduledAttractionEntity sae : tripScheduleAttrs) {
			 scheduleOfAttr = getScheduleOfAttraction(userId,sae.getAttrId(),trip.getStart(),trip.getEnd());
			 if(scheduleOfAttr != null) {
				 scheduleOfAttr.removeAll(allScheduleOfAttr);
				 allScheduleOfAttr.addAll(scheduleOfAttr);
				 addUsersScheduleToMap(userId, scheduleOfAttr,users, 1);
			 }
			 points.add(new Point(sae.getLatitude(),sae.getLongitude()));
		 }

		//all user with the same attractions (not scheduled by the me), other schedules
		 for(ShortDetailedAttraction ae : trip.getNotScheduledYetAttractions()) {
			 scheduleOfAttr = getScheduleOfAttraction(userId,ae.getId(),trip.getStart(),trip.getEnd());
			 if(scheduleOfAttr != null) {
				 scheduleOfAttr.removeAll(allScheduleOfAttr);
				 allScheduleOfAttr.addAll(scheduleOfAttr);
				 addUsersScheduleToMap(userId, scheduleOfAttr,users, 1);
			 }
			 points.add(new Point(ae.getLatitude(),ae.getLongitude()));
		 }
		
		if(!points.isEmpty()) {
			//get all attraction in a bound that his center is the average of all point.
			BoundingBox bb = calculateMapBound(points);
			 Set<ShortDetailedAttraction> attrsBybound = getAttractions(userId,bb.getNorth(), bb.getEast(), bb.getSouth(), bb.getWest(), null, null);
			 
			 if(attrsBybound != null) {
				 for(ShortDetailedAttraction ae : attrsBybound) {
					 scheduleOfAttr = getScheduleOfAttraction(userId,ae.getId(),trip.getStart(),trip.getEnd());
					 scheduleOfAttr.removeAll(allScheduleOfAttr);
					 allScheduleOfAttr.addAll(scheduleOfAttr);
					 addUsersScheduleToMap(userId, scheduleOfAttr,users, 2);
				 }
			 }
		}
				
		LinkedList<UserSuggestion> suggestions = getListOfSuggestedUsers(users, -1);
		return new UserNotification<UserSuggestion>(userId, NotificationType.USERS_NEAR_ME,suggestions);		
	}

	
	@Override
	public void createTripSharingRequest(long userId, long shareTripId,
			long shareWith) throws IOException {
		
		logger.log(Level.SEVERE, "creating TripSharingRequest from " + userId + " of trip " + shareTripId + "to " + shareWith);			

		PersistenceManager pm = getPersistenceManager();

		try {
			UserEntity shareWithUser = getUser(pm, shareWith);
			if(shareWithUser == null) {
				throw new IOException("user (" + shareWith + ") is not a registered Trippin Out User");
			}
			
			TripEntity trip = pm.getObjectById(TripEntity.class, createKey(TripEntity.class, shareTripId));
			if(trip.getUserId() != userId) {
				throw new IOException("user (" + userId + ") is not the owner of this trip (" + shareTripId + ")");
			}
			else if(trip.isUserSharedWith(shareWith)) {
				throw new IOException("trip (" + shareTripId + ") is already shared with user (" + shareWith + ")");
			}
			else if (trip.isUserPendingShareInvite(shareWith)) {
				throw new IOException("sharing invitation from trip (" + shareTripId + ") already sent to user (" + shareWith + ")");
			}
			else {
				SharingRequest sharingTripsRequest = new SharingRequest(trip, userId);
				shareWithUser.addSharingTripRequest(sharingTripsRequest);
				sharingTripsRequest.setId();
				logger.log(Level.SEVERE, "SharingRequest created with id " + sharingTripsRequest.getId() + " by user " + userId + " and sent to user " + shareWithUser.getUserId());
				trip.addUserToPendingShareInvites(shareWith);

				userCollectionsValidator(pm,shareWithUser);
				TripEntity detachedTrip = tripCollectionsValidator(pm, trip, true);
				
				logger.log(Level.SEVERE, "trip " + detachedTrip.getId() + "has pending sharing requests to " + detachedTrip.getAllUsersSharingInvites());			
			}
			
		} finally {
			pm.close();
		}
	}

	
	@Override
	public UserNotification<SharingRequest> getUserTripSharingRequests(
			long userId) throws IOException {
		UserEntity user = getUserById(userId);
		if(user == null) {
			throw new IOException("user (" + userId + ") is not a registered Trippin Out User");
		}
		 
		List<SharingRequest> notifications = user.getSharingTripsRequests();
		if(notifications == null) {
			notifications = new ArrayList<SharingRequest>();
		}
		
		return new UserNotification<SharingRequest>(userId, NotificationType.TRIP_SHARING, notifications);
	}
	

	@Override
	public UserNotification<SharingRequest> acceptTripSharingRequset(long userId, long sharingReqId) throws IOException {
		return acceptOrDeniedTripSharingRequest(userId, sharingReqId, true);
	}
	

	@Override
	public UserNotification<SharingRequest> deniedTripSharingRequset(long userId, long sharingReqId) throws IOException {
		return acceptOrDeniedTripSharingRequest(userId, sharingReqId, false);		
	}
	
	
	private BoundingBox calculateMapBound(List<Point> points) {
		double LAT = 6.5;
		double LON = 3;
		
		double sumLats = 0;
		double sumLons = 0;
		double numOfPoint = points.size();
		for(Point p : points) {
			sumLats += p.getLat();
			sumLons += p.getLon();
		}
			
		double averageLat = sumLats / numOfPoint;
		double averageLon = sumLons / numOfPoint;
		
		double latN = averageLat + LAT;
		double lonE = averageLon + LON;
		double latS = averageLat - LAT;
		double lonW = averageLon - LON;
		return new BoundingBox(latN, lonE, latS, lonW);
	}
	
	/**
	 * 
	 * @param userId
	 * @param scheduleAttrs
	 * @param users
	 * @param isSame : 0 - same schedule as the user
	 * 				   1 - not same schedule as the user
	 * 				   2 - user does not attend to the attraction
	 */
	private static void addUsersScheduleToMap(long userId, Iterable<ScheduledAttractionEntity> scheduleAttrs, 
			Map<Long, UserSuggestion> users, int isSame) {
		for(ScheduledAttractionEntity sce : scheduleAttrs) {
			for(long attendee : sce.getAttendees()) {
				if(attendee == userId) {
					continue;
				}
				UserSuggestion userSuggestion = users.get(attendee);
				if(userSuggestion == null) {
					userSuggestion = new UserSuggestion(attendee);
					users.put(attendee, userSuggestion);
				}
				if(isSame == 0) {
					userSuggestion.addSimilarAttractionSchecule(sce);
				}
				else if(isSame == 1) {
					userSuggestion.addSimilarAttractionNotSameSchecule(sce);
				}
				else {
					userSuggestion.addScheduleAttractionsNotAttend(sce);
				}
			}			
		}
	}
	
	@SuppressWarnings("unused")
	private static void addUsersAttractionsToMap(long userId, Iterable<ShortDetailedAttraction> Attrs, Map<Long, UserSuggestion> users) {
		for(ShortDetailedAttraction sda : Attrs) {
			for(long attendee : sda.getNotScheduledYetAttendees()) {
				if(attendee == userId) {
					continue;
				}
				UserSuggestion userSuggestion = users.get(attendee);
				if(userSuggestion == null) {
					userSuggestion = new UserSuggestion(attendee);
					users.put(attendee, userSuggestion);
				}
				userSuggestion.addSimilarAttraction(sda);
			}			
		}
	}
	
	private static LinkedList<UserSuggestion> getListOfSuggestedUsers(Map<Long, UserSuggestion> users, int limit) {
		Set<UserSuggestion> userSuggestionsSet = new TreeSet<UserSuggestion>(new Comparator<UserSuggestion>() {
			@Override
			public int compare(UserSuggestion us1, UserSuggestion us2) {
				if(us1.getNumOfSameScheduleAttrs() > us2.getNumOfSameScheduleAttrs()) {
					return 1;
				}
				else if(us1.getNumOfSameScheduleAttrs() < us2.getNumOfSameScheduleAttrs()) {
					return -1;
				}
				else if(us1.getNumOfSameScheduleAttrs() > us2.getNumOfSameScheduleAttrs()) {
					return 1;
				}
				else if(us1.getNumOfNotSameScheduleAttrs() < us2.getNumOfNotSameScheduleAttrs()) {
					return -1;
				}
				else if(us1.getNumOfNotSameScheduleAttrs() > us2.getNumOfNotSameScheduleAttrs()) {
					return 1;
				}
				else if(us1.getNumOfTotalAttractions() > us2.getNumOfTotalAttractions()) {
					return 1;
				}
				else {
					return -1;
				}
			}			
		});

		userSuggestionsSet.addAll(users.values());

		LinkedList<UserSuggestion> userSuggestionsResult = new LinkedList<UserSuggestion>();
		for(UserSuggestion suggestion : userSuggestionsSet) {
			userSuggestionsResult.add(suggestion);
			if((limit > 0) && (userSuggestionsResult.size() >= limit)) {
				break;
			}			
		}

		return userSuggestionsResult;
	}
	
	
	private UserNotification<SharingRequest> acceptOrDeniedTripSharingRequest(long userId, long sharingReqId, boolean accept) throws IOException {
		PersistenceManager pm = getPersistenceManager();

		UserNotification<SharingRequest> sharingRequests = null;
		try {
			UserEntity user = getUser(pm, userId);
			if(user == null) {
				throw new IOException("user (" + userId + ") is not a registered Trippin Out User");
			}
			
			SharingRequest req = user.getSharingTripsRequestById(sharingReqId);

			if(req == null) {
				throw new IOException("user (" + userId + ") does not have request (" + sharingReqId + ")");
			}
			
			TripEntity trip = pm.getObjectById(TripEntity.class, createKey(TripEntity.class, req.getShareEntityId()));

			if(accept) {
				trip.addUserToSharedWith(userId);
			}
			else {
				trip.removeUserFromPendingShareInvites(userId);
			}
			
			user.removeSharingTripRequest(sharingReqId);
			pm.deletePersistent(req);
			
			tripCollectionsValidator(pm, trip, true);
			
			user.getSharingTripsRequests().size();
			UserEntity detachedUser = userCollectionsValidator(pm, user);
			
			sharingRequests = new UserNotification<SharingRequest>(userId,NotificationType.TRIP_SHARING,detachedUser.getSharingTripsRequests());
			
		} finally {
			pm.close();
		}
		
		return sharingRequests;
	}
	
	
	private static class ShortAttrsYouMayLikeComparator implements Comparator<ShortDetailedAttraction> {

		private List<AttractionTypeCluster> orderedClusters;
		
		public ShortAttrsYouMayLikeComparator(List<AttractionTypeCluster> orderedClusters) {
			this.orderedClusters = orderedClusters;
		}
		@Override
		public int compare(ShortDetailedAttraction sda1,
				ShortDetailedAttraction sda2) {
			List<AttractionTypeCluster> attr1Clusters = AttractionTypeCluster.getTypeClusters(
					AttractionType.getAttractionTypeByName(sda1.getType()));
			List<AttractionTypeCluster> attr2Clusters = AttractionTypeCluster.getTypeClusters(
					AttractionType.getAttractionTypeByName(sda2.getType()));
			
			int indexType1 = getFirstIndexOf(attr1Clusters);
			int indexType2 = getFirstIndexOf(attr2Clusters);
			if((indexType1 < indexType2) && (indexType1 > -1)) {
				return 1;
			}
			else if((indexType1 > indexType2) && (indexType2 > -1)) {
				return -1;
			}
			
			int numOfAttendees1 = sda1.getFutureAttendees().size() + sda1.getNotScheduledYetAttendees().size();
			int numOfAttendees2 = sda2.getFutureAttendees().size() + sda2.getNotScheduledYetAttendees().size();
			
			if(numOfAttendees1 > numOfAttendees2) {
				return 1;
			}
			else if(numOfAttendees1 < numOfAttendees2) {
				return -1;
			}
			
			long rating1 = sda1.getPluses() - sda1.getMinuses();
			long rating2 = sda1.getPluses() - sda2.getMinuses();
			
			if(rating1 > rating2) {
				return 1;
			}
			else {
				return -1;
			}
		}
		
		
		private int getFirstIndexOf(List<AttractionTypeCluster> attrClusters) {
			int firstIndex = -1;
			for(AttractionTypeCluster cluster : attrClusters) {
				int index = orderedClusters.indexOf(cluster);
				if((index != -1) && ((firstIndex == -1) || (index < firstIndex))) {
					firstIndex = index;
				}
			}
			
			return firstIndex;
		}		
	}

	//--------------------------------------Sticky Notes Handling--------------------------------------------\\

	@Override
	public Set<StickyNoteEntity> getStickyNotesByBound(long userId, double latN,
			double lonE, double latS, double lonW, Date startTime, Date endTime) {
		
		BoundingBox bb = new BoundingBox(latN, lonE, latS, lonW);
		
		// Calculate the geocells list to be used in the queries (optimize list of cells that complete the given bounding box)
		List<String> cells = GeocellManager.bestBboxSearchCells(bb, null);

		PersistenceManager pm = getPersistenceManager();

		String queryString = "SELECT FROM com.trippin.DB.entities.StickyNoteEntity WHERE geocellsParameter.contains(geoCellsData)";
		Query query = pm.newQuery(queryString);
		query.declareParameters("String geocellsParameter");

		Set<StickyNoteEntity> returnedStickyNotes = new TreeSet<StickyNoteEntity>(new StickyNoteComparator());
		try {
			@SuppressWarnings("unchecked")
			List<StickyNoteEntity> stickyNotes = (List<StickyNoteEntity>) query.execute(cells);
			stickyNotes.size();
			
			for(StickyNoteEntity sne : stickyNotes) {
				returnedStickyNotes.add(pm.detachCopy(sne));
			}
			
		} finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}

		return returnedStickyNotes;
	}
	
	
	@Override
	public StickyNoteEntity createStickyNote(long userId, StickyNoteEntity sticktNote) throws IOException {
		PersistenceManager pm = getPersistenceManager();		

		StickyNoteEntity stcknt = new StickyNoteEntity(userId, sticktNote.getLongitude(), sticktNote.getLatitude(),
				sticktNote.getTitle(), sticktNote.getStart(), sticktNote.getEnd(), sticktNote.getDescription());

		StickyNoteEntity returnedStickyNotes = null;
		try {
			UserEntity user = getUser(pm, userId);
			if(user == null) {
				throw new IOException("user (" + userId + ") is not a registered Trippin Out User");
			}
			user.addStickyNote(stcknt);
			stcknt.setId();
			
			userCollectionsValidator(pm, user);

			returnedStickyNotes = pm.detachCopy(stcknt);
		}
		finally {
			pm.close();
		}
		return returnedStickyNotes;		
	}
	
	@Override
	public Set<StickyNoteEntity> getAllStickyNoteOfUserStickyNote(long userId, long stickyNoteId) throws IOException {
		PersistenceManager pm = getPersistenceManager();

		Set<StickyNoteEntity> returnedStickyNotes = null;
		StickyNoteEntity stkNote = null;
		try {
			UserEntity user = getUser(pm, userId);
			if(user == null) {
				throw new IOException("user (" + userId + ") is not a registered Trippin Out User");
			}
			
			UserEntity detachedUser = userCollectionsValidator(pm, user);
			StickyNoteEntity temp = detachedUser.getStickyNoteById(stickyNoteId);

			if(temp == null) {
				throw new JDOObjectNotFoundException("user (" + userId + ") does not have sticky note (" + stickyNoteId + ")");
			}
			
			stkNote = pm.detachCopy(temp);

		} finally {
			pm.close();
		}		

		BoundingBox bb = stkNote.getBoundingBox();
		returnedStickyNotes = getStickyNotesByBound(userId, bb.getNorth(), bb.getEast(), bb.getSouth(), bb.getWest(),
				stkNote.getStart(), stkNote.getEnd());

		return returnedStickyNotes;
	}
		
	
	@Override
	public List<StickyNoteEntity> getAllUserStickyNotes(long userId) {
		PersistenceManager pm = getPersistenceManager();

		Query query = null;
		List<StickyNoteEntity> returnedStickyNotes = new ArrayList<StickyNoteEntity>();
		try {
			query = pm.newQuery(StickyNoteEntity.class, "userId == " + userId);
			@SuppressWarnings("unchecked")
			List<StickyNoteEntity> stickyNotes =  (List<StickyNoteEntity>) query.execute();
			stickyNotes.size();

			for(StickyNoteEntity sne : stickyNotes) {
				returnedStickyNotes.add(pm.detachCopy(sne));
			}

		} finally {
			if(query != null) {
				query.closeAll();
			}
			pm.close();
		}		
		return returnedStickyNotes;
	}
	
	
	@Override
	public Set<StickyNoteEntity> deleteStickyNote(long userId, long stickyNoteId) throws IOException {
		PersistenceManager pm = getPersistenceManager();

		Set<StickyNoteEntity> returnStickyNotes = null;
		try {			

			UserEntity user = getUser(pm, userId);
			if(user == null) {
				throw new IOException("user (" + userId + ") is not a registered Trippin Out User");
			}
			StickyNoteEntity stkNote = user.getStickyNoteById(stickyNoteId);
			if(stkNote == null) {
				throw new JDOObjectNotFoundException("user (" + userId + ") does not have sticky note (" + stickyNoteId + ")");
			}
			user.removeStickyNote(stickyNoteId);			
			pm.deletePersistent(stkNote);
			
			UserEntity detachedUser = userCollectionsValidator(pm, user);
			
			returnStickyNotes = detachedUser.getStickyNotes();
		}
		finally {
			pm.close();
		}
		return returnStickyNotes;
	}

	
}
	

